//===-- HashedNameToDIE.cpp -------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "HashedNameToDIE.h"

void
DWARFMappedHash::ExtractDIEArray (const DIEInfoArray &die_info_array, DIEArray &die_offsets)
{
    const size_t count = die_info_array.size();
    for (size_t i=0; i<count; ++i)
        die_offsets.emplace_back(die_info_array[i].cu_offset, die_info_array[i].offset);
}

void
DWARFMappedHash::ExtractDIEArray (const DIEInfoArray &die_info_array,
                                  const dw_tag_t tag,
                                  DIEArray &die_offsets)
{
    if (tag == 0)
    {
        ExtractDIEArray (die_info_array, die_offsets);
    }
    else
    {
        const size_t count = die_info_array.size();
        for (size_t i=0; i<count; ++i)
        {
            const dw_tag_t die_tag = die_info_array[i].tag;
            bool tag_matches = die_tag == 0 || tag == die_tag;
            if (!tag_matches)
            {
                if (die_tag == DW_TAG_class_type || die_tag == DW_TAG_structure_type)
                    tag_matches = tag == DW_TAG_structure_type || tag == DW_TAG_class_type;
            }
            if (tag_matches)
                die_offsets.emplace_back(die_info_array[i].cu_offset, die_info_array[i].offset);
        }
    }
}

void
DWARFMappedHash::ExtractDIEArray (const DIEInfoArray &die_info_array,
                                  const dw_tag_t tag,
                                  const uint32_t qualified_name_hash,
                                  DIEArray &die_offsets)
{
    if (tag == 0)
    {
        ExtractDIEArray (die_info_array, die_offsets);
    }
    else
    {
        const size_t count = die_info_array.size();
        for (size_t i=0; i<count; ++i)
        {
            if (qualified_name_hash != die_info_array[i].qualified_name_hash)
                continue;
            const dw_tag_t die_tag = die_info_array[i].tag;
            bool tag_matches = die_tag == 0 || tag == die_tag;
            if (!tag_matches)
            {
                if (die_tag == DW_TAG_class_type || die_tag == DW_TAG_structure_type)
                    tag_matches = tag == DW_TAG_structure_type || tag == DW_TAG_class_type;
            }
            if (tag_matches)
                die_offsets.emplace_back(die_info_array[i].cu_offset, die_info_array[i].offset);
        }
    }
}

void
DWARFMappedHash::ExtractClassOrStructDIEArray (const DIEInfoArray &die_info_array,
                                               bool return_implementation_only_if_available,
                                               DIEArray &die_offsets)
{
    const size_t count = die_info_array.size();
    for (size_t i=0; i<count; ++i)
    {
        const dw_tag_t die_tag = die_info_array[i].tag;
        if (die_tag == 0 || die_tag == DW_TAG_class_type || die_tag == DW_TAG_structure_type)
        {
            if (die_info_array[i].type_flags & eTypeFlagClassIsImplementation)
            {
                if (return_implementation_only_if_available)
                {
                    // We found the one true definition for this class, so
                    // only return that
                    die_offsets.clear();                        
                    die_offsets.emplace_back(die_info_array[i].cu_offset, die_info_array[i].offset);
                    return;
                }
                else
                {
                    // Put the one true definition as the first entry so it
                    // matches first
                    die_offsets.emplace(die_offsets.begin(), die_info_array[i].cu_offset, die_info_array[i].offset);
                }
            }
            else
            {
                die_offsets.emplace_back(die_info_array[i].cu_offset, die_info_array[i].offset);
            }
        }
    }
}

void
DWARFMappedHash::ExtractTypesFromDIEArray (const DIEInfoArray &die_info_array,
                                           uint32_t type_flag_mask,
                                           uint32_t type_flag_value,
                                           DIEArray &die_offsets)
{
    const size_t count = die_info_array.size();
    for (size_t i=0; i<count; ++i)
    {
        if ((die_info_array[i].type_flags & type_flag_mask) == type_flag_value)
            die_offsets.emplace_back(die_info_array[i].cu_offset, die_info_array[i].offset);
    }
}

const char *
DWARFMappedHash::GetAtomTypeName (uint16_t atom)
{
    switch (atom)
    {
        case eAtomTypeNULL:         return "NULL";
        case eAtomTypeDIEOffset:    return "die-offset";
        case eAtomTypeCUOffset:     return "cu-offset";
        case eAtomTypeTag:          return "die-tag";
        case eAtomTypeNameFlags:    return "name-flags";
        case eAtomTypeTypeFlags:    return "type-flags";
        case eAtomTypeQualNameHash: return "qualified-name-hash";
        case eAtomTypeString:       return "strp";
    }
    return "<invalid>";
}

DWARFMappedHash::DIEInfo::DIEInfo () :
    cu_offset (DW_INVALID_OFFSET),
    offset (DW_INVALID_OFFSET),
    tag (0),
    type_flags (0),
    qualified_name_hash (0),
    strp (UINT64_MAX)
{
}

DWARFMappedHash::DIEInfo::DIEInfo (dw_offset_t c,
                                   dw_offset_t o,
                                   dw_tag_t t,
                                   uint32_t f,
                                   uint32_t h,
                                   uint64_t s) :
    cu_offset (c),
    offset (o),
    tag (t),
    type_flags (f),
    qualified_name_hash (h),
    strp (s)
{
}

DWARFMappedHash::Prologue::Prologue (dw_offset_t _die_base_offset) :
    die_base_offset (_die_base_offset),
    atoms(),
    atom_mask (0),
    min_hash_data_byte_size(0),
    hash_data_has_fixed_byte_size(true)
{
    // Define an array of DIE offsets by first defining an array, 
    // and then define the atom type for the array, in this case
    // we have an array of DIE offsets
    AppendAtom (eAtomTypeDIEOffset, DW_FORM_data4);
}

void
DWARFMappedHash::Prologue::ClearAtoms ()
{
    hash_data_has_fixed_byte_size = true;
    min_hash_data_byte_size = 0;
    atom_mask = 0;
    atoms.clear();
}

bool
DWARFMappedHash::Prologue::ContainsAtom (AtomType atom_type) const
{
    return (atom_mask & (1u << atom_type)) != 0;
}

void
DWARFMappedHash::Prologue::Clear ()
{
    die_base_offset = 0;
    ClearAtoms ();
}

void
DWARFMappedHash::Prologue::AppendAtom (AtomType type, dw_form_t form)
{
    atoms.push_back ({type, form});
    atom_mask |= 1u << type;
    switch (form)
    {
        case DW_FORM_indirect:
        case DW_FORM_exprloc:
        case DW_FORM_flag_present:
        case DW_FORM_ref_sig8:
            assert (!"Unhandled atom form");
            break;

        case DW_FORM_string:
        case DW_FORM_block:
        case DW_FORM_block1:
        case DW_FORM_sdata:
        case DW_FORM_udata:
        case DW_FORM_ref_udata:
        case DW_FORM_GNU_addr_index:
        case DW_FORM_GNU_str_index:
            hash_data_has_fixed_byte_size = false;
            LLVM_FALLTHROUGH;
        case DW_FORM_flag:
        case DW_FORM_data1:
        case DW_FORM_ref1:
        case DW_FORM_sec_offset:
            min_hash_data_byte_size += 1; 
            break;

        case DW_FORM_block2:
            hash_data_has_fixed_byte_size = false;
            LLVM_FALLTHROUGH;
        case DW_FORM_data2: 
        case DW_FORM_ref2:
            min_hash_data_byte_size += 2; 
            break;

        case DW_FORM_block4: 
            hash_data_has_fixed_byte_size = false;
            LLVM_FALLTHROUGH;
        case DW_FORM_data4:
        case DW_FORM_ref4:
        case DW_FORM_addr:
        case DW_FORM_ref_addr:
        case DW_FORM_strp:
            min_hash_data_byte_size += 4; 
            break;

        case DW_FORM_data8:
        case DW_FORM_ref8:
            min_hash_data_byte_size += 8; 
            break;
            
    }
}

lldb::offset_t
DWARFMappedHash::Prologue::Read (const lldb_private::DataExtractor &data,
      lldb::offset_t offset)
{
    ClearAtoms ();
    
    die_base_offset = data.GetU32 (&offset);
    
    const uint32_t atom_count = data.GetU32 (&offset);
    if (atom_count == 0x00060003u)
    {
        // Old format, deal with contents of old pre-release format
        while (data.GetU32(&offset))
            /* do nothing */;

        // Hardcode to the only known value for now.
        AppendAtom (eAtomTypeDIEOffset, DW_FORM_data4);
    }
    else
    {
        for (uint32_t i=0; i<atom_count; ++i)
        {
            AtomType type = (AtomType)data.GetU16 (&offset);
            dw_form_t form = (dw_form_t)data.GetU16 (&offset);                    
            AppendAtom (type, form);
        }
    }
    return offset;
}

size_t
DWARFMappedHash::Prologue::GetByteSize () const
{
    // Add an extra count to the atoms size for the zero termination Atom that gets
    // written to disk
    return sizeof(die_base_offset) + sizeof(uint32_t) + atoms.size() * sizeof(Atom);
}

size_t
DWARFMappedHash::Prologue::GetMinimumHashDataByteSize () const
{
    return min_hash_data_byte_size;
}

bool
DWARFMappedHash::Prologue::HashDataHasFixedByteSize() const
{
    return hash_data_has_fixed_byte_size;
}

size_t
DWARFMappedHash::Header::GetByteSize (const HeaderData &header_data)
{
    return header_data.GetByteSize();
}

lldb::offset_t
DWARFMappedHash::Header::Read (lldb_private::DataExtractor &data, lldb::offset_t offset)
{
    offset = MappedHash::Header<Prologue>::Read (data, offset);
    if (offset != UINT32_MAX)
    {
        offset = header_data.Read (data, offset);
    }
    return offset;
}

bool
DWARFMappedHash::Header::Read (const lldb_private::DWARFDataExtractor &data, 
                               lldb::offset_t *offset_ptr, 
                               DIEInfo &hash_data) const
{
    const size_t num_atoms = header_data.atoms.size();
    if (num_atoms == 0)
        return false;
    
    for (size_t i=0; i<num_atoms; ++i)
    {
        DWARFFormValue form_value (NULL, header_data.atoms[i].form);
        
        if (!form_value.ExtractValue(data, offset_ptr))
            return false;
        
        switch (header_data.atoms[i].type)
        {
            case eAtomTypeDIEOffset:    // DIE offset, check form for encoding
                hash_data.offset = (dw_offset_t)form_value.Reference (header_data.die_base_offset);
                break;

            case eAtomTypeTag:          // DW_TAG value for the DIE
                hash_data.tag = (dw_tag_t)form_value.Unsigned ();
                break;

            case eAtomTypeTypeFlags:    // Flags from enum TypeFlags
                hash_data.type_flags = (uint32_t)form_value.Unsigned ();
                break;

            case eAtomTypeQualNameHash:    // Flags from enum TypeFlags
                hash_data.qualified_name_hash = form_value.Unsigned ();
                break;

            default:
                // We can always skip atoms we don't know about
                break;
        }
    }
    return true;
}

void
DWARFMappedHash::Header::Dump (lldb_private::Stream& strm, const DIEInfo &hash_data) const
{
    const size_t num_atoms = header_data.atoms.size();
    for (size_t i=0; i<num_atoms; ++i)
    {
        if (i > 0)
            strm.PutCString (", ");
        
        DWARFFormValue form_value (NULL, header_data.atoms[i].form);
        switch (header_data.atoms[i].type)
        {
            case eAtomTypeDIEOffset:    // DIE offset, check form for encoding
                strm.Printf ("{0x%8.8x}", hash_data.offset);
                break;

            case eAtomTypeTag:          // DW_TAG value for the DIE
                {
                    const char *tag_cstr = lldb_private::DW_TAG_value_to_name (hash_data.tag);
                    if (tag_cstr)
                        strm.PutCString (tag_cstr);
                    else
                        strm.Printf ("DW_TAG_(0x%4.4x)", hash_data.tag);
                }
                break;

            case eAtomTypeTypeFlags:    // Flags from enum TypeFlags
                strm.Printf ("0x%2.2x", hash_data.type_flags);
                if (hash_data.type_flags)
                {
                    strm.PutCString (" (");
                    if (hash_data.type_flags & eTypeFlagClassIsImplementation)
                        strm.PutCString (" implementation");
                    strm.PutCString (" )");
                }
                break;

            case eAtomTypeQualNameHash:    // Flags from enum TypeFlags
                strm.Printf ("0x%8.8x", hash_data.qualified_name_hash);
                break;

            default:
                strm.Printf ("AtomType(0x%x)", header_data.atoms[i].type);
                break;
        }
    }
}

DWARFMappedHash::MemoryTable::MemoryTable (lldb_private::DWARFDataExtractor &table_data, 
                                           const lldb_private::DWARFDataExtractor &string_table,
                                           const char *name) :
    MappedHash::MemoryTable<uint32_t, Header, DIEInfoArray> (table_data),
    m_data (table_data),
    m_string_table (string_table),
    m_name (name)
{
}

const char *
DWARFMappedHash::MemoryTable::GetStringForKeyType (KeyType key) const
{
    // The key in the DWARF table is the .debug_str offset for the string
    return m_string_table.PeekCStr (key);
}

bool
DWARFMappedHash::MemoryTable::ReadHashData (uint32_t hash_data_offset, HashData &hash_data) const
{
    lldb::offset_t offset = hash_data_offset;
    offset += 4; // Skip string table offset that contains offset of hash name in .debug_str
    const uint32_t count = m_data.GetU32 (&offset);
    if (count > 0)
    {
        hash_data.resize(count);
        for (uint32_t i=0; i<count; ++i)
        {
            if (!m_header.Read(m_data, &offset, hash_data[i]))
                return false;
        }
    }
    else
        hash_data.clear();
    return true;
}

DWARFMappedHash::MemoryTable::Result
DWARFMappedHash::MemoryTable::GetHashDataForName (const char *name,
                                                  lldb::offset_t* hash_data_offset_ptr,
                                                  Pair &pair) const
{
    pair.key = m_data.GetU32 (hash_data_offset_ptr);
    pair.value.clear();

    // If the key is zero, this terminates our chain of HashData objects
    // for this hash value.
    if (pair.key == 0)
        return eResultEndOfHashData;

    // There definitely should be a string for this string offset, if
    // there isn't, there is something wrong, return and error
    const char *strp_cstr = m_string_table.PeekCStr (pair.key);
    if (strp_cstr == NULL)
    {
        *hash_data_offset_ptr = UINT32_MAX;
        return eResultError;
    }

    const uint32_t count = m_data.GetU32 (hash_data_offset_ptr);
    const size_t min_total_hash_data_size = count * m_header.header_data.GetMinimumHashDataByteSize();
    if (count > 0 && m_data.ValidOffsetForDataOfSize (*hash_data_offset_ptr, min_total_hash_data_size))
    {
        // We have at least one HashData entry, and we have enough
        // data to parse at least "count" HashData entries.
        
        // First make sure the entire C string matches...
        const bool match = strcmp (name, strp_cstr) == 0;
        
        if (!match && m_header.header_data.HashDataHasFixedByteSize())
        {
            // If the string doesn't match and we have fixed size data,
            // we can just add the total byte size of all HashData objects
            // to the hash data offset and be done...
            *hash_data_offset_ptr += min_total_hash_data_size;
        }
        else
        {
            // If the string does match, or we don't have fixed size data
            // then we need to read the hash data as a stream. If the
            // string matches we also append all HashData objects to the
            // value array.
            for (uint32_t i=0; i<count; ++i)
            {
                DIEInfo die_info;
                if (m_header.Read(m_data, hash_data_offset_ptr, die_info))
                {
                    // Only happened if the HashData of the string matched...
                    if (match)
                        pair.value.push_back (die_info);
                }
                else
                {
                    // Something went wrong while reading the data
                    *hash_data_offset_ptr = UINT32_MAX;
                    return eResultError;
                }
            }
        }
        // Return the correct response depending on if the string matched
        // or not...
        if (match)
            return eResultKeyMatch;     // The key (cstring) matches and we have lookup results!
        else
            return eResultKeyMismatch;  // The key doesn't match, this function will get called 
                                        // again for the next key/value or the key terminator
                                        // which in our case is a zero .debug_str offset.
    }
    else
    {
        *hash_data_offset_ptr = UINT32_MAX;
        return eResultError;
    }
}

DWARFMappedHash::MemoryTable::Result
DWARFMappedHash::MemoryTable::AppendHashDataForRegularExpression (
        const lldb_private::RegularExpression& regex,
        lldb::offset_t* hash_data_offset_ptr, 
        Pair &pair) const
{
    pair.key = m_data.GetU32 (hash_data_offset_ptr);
    // If the key is zero, this terminates our chain of HashData objects
    // for this hash value.
    if (pair.key == 0)
        return eResultEndOfHashData;
    
    // There definitely should be a string for this string offset, if
    // there isn't, there is something wrong, return and error
    const char *strp_cstr = m_string_table.PeekCStr (pair.key);
    if (strp_cstr == NULL)
        return eResultError;
    
    const uint32_t count = m_data.GetU32 (hash_data_offset_ptr);
    const size_t min_total_hash_data_size = count * m_header.header_data.GetMinimumHashDataByteSize();
    if (count > 0 && m_data.ValidOffsetForDataOfSize (*hash_data_offset_ptr, min_total_hash_data_size))
    {
        const bool match = regex.Execute(strp_cstr);
        
        if (!match && m_header.header_data.HashDataHasFixedByteSize())
        {
            // If the regex doesn't match and we have fixed size data,
            // we can just add the total byte size of all HashData objects
            // to the hash data offset and be done...
            *hash_data_offset_ptr += min_total_hash_data_size;
        }
        else
        {
            // If the string does match, or we don't have fixed size data
            // then we need to read the hash data as a stream. If the
            // string matches we also append all HashData objects to the
            // value array.
            for (uint32_t i=0; i<count; ++i)
            {
                DIEInfo die_info;
                if (m_header.Read(m_data, hash_data_offset_ptr, die_info))
                {
                    // Only happened if the HashData of the string matched...
                    if (match)
                        pair.value.push_back (die_info);
                }
                else
                {
                    // Something went wrong while reading the data
                    *hash_data_offset_ptr = UINT32_MAX;
                    return eResultError;
                }
            }
        }
        // Return the correct response depending on if the string matched
        // or not...
        if (match)
            return eResultKeyMatch;     // The key (cstring) matches and we have lookup results!
        else
            return eResultKeyMismatch;  // The key doesn't match, this function will get called 
                                        // again for the next key/value or the key terminator
                                        // which in our case is a zero .debug_str offset.
    }
    else
    {
        *hash_data_offset_ptr = UINT32_MAX;
        return eResultError;
    }
}

size_t
DWARFMappedHash::MemoryTable::AppendAllDIEsThatMatchingRegex (
        const lldb_private::RegularExpression& regex, 
        DIEInfoArray &die_info_array) const
{
    const uint32_t hash_count = m_header.hashes_count;
    Pair pair;
    for (uint32_t offset_idx=0; offset_idx<hash_count; ++offset_idx)
    {
        lldb::offset_t hash_data_offset = GetHashDataOffset (offset_idx);
        while (hash_data_offset != UINT32_MAX)
        {
            const lldb::offset_t prev_hash_data_offset = hash_data_offset;
            Result hash_result = AppendHashDataForRegularExpression (regex, &hash_data_offset, pair);
            if (prev_hash_data_offset == hash_data_offset)
                break;

            // Check the result of getting our hash data
            switch (hash_result)
            {
                case eResultKeyMatch:
                case eResultKeyMismatch:
                    // Whether we matches or not, it doesn't matter, we
                    // keep looking.
                    break;
                    
                case eResultEndOfHashData:
                case eResultError:
                    hash_data_offset = UINT32_MAX;
                    break;
            }
        }
    }
    die_info_array.swap (pair.value);
    return die_info_array.size();
}

size_t
DWARFMappedHash::MemoryTable::AppendAllDIEsInRange (const uint32_t die_offset_start, 
                                                    const uint32_t die_offset_end, 
                                                    DIEInfoArray &die_info_array) const
{
    const uint32_t hash_count = m_header.hashes_count;
    for (uint32_t offset_idx=0; offset_idx<hash_count; ++offset_idx)
    {
        bool done = false;
        lldb::offset_t hash_data_offset = GetHashDataOffset (offset_idx);
        while (!done && hash_data_offset != UINT32_MAX)
        {
            KeyType key = m_data.GetU32 (&hash_data_offset);
            // If the key is zero, this terminates our chain of HashData objects
            // for this hash value.
            if (key == 0)
                break;
            
            const uint32_t count = m_data.GetU32 (&hash_data_offset);
            for (uint32_t i=0; i<count; ++i)
            {
                DIEInfo die_info;
                if (m_header.Read(m_data, &hash_data_offset, die_info))
                {
                    if (die_info.offset == 0)
                        done = true;
                    if (die_offset_start <= die_info.offset && die_info.offset < die_offset_end)
                        die_info_array.push_back(die_info);
                }
            }
        }
    }
    return die_info_array.size();
}

size_t
DWARFMappedHash::MemoryTable::FindByName (const char *name, DIEArray &die_offsets)
{
    if (!name || !name[0])
        return 0;
    
    DIEInfoArray die_info_array;
    if (FindByName(name, die_info_array))
        DWARFMappedHash::ExtractDIEArray (die_info_array, die_offsets);
    return die_info_array.size();
}

size_t
DWARFMappedHash::MemoryTable::FindByNameAndTag (const char *name,
                                                const dw_tag_t tag,
                                                DIEArray &die_offsets)
{
    DIEInfoArray die_info_array;
    if (FindByName(name, die_info_array))
        DWARFMappedHash::ExtractDIEArray (die_info_array, tag, die_offsets);
    return die_info_array.size();
}

size_t
DWARFMappedHash::MemoryTable::FindByNameAndTagAndQualifiedNameHash (const char *name,
                                                                    const dw_tag_t tag,
                                                                    const uint32_t qualified_name_hash,
                                                                    DIEArray &die_offsets)
{
    DIEInfoArray die_info_array;
    if (FindByName(name, die_info_array))
        DWARFMappedHash::ExtractDIEArray (die_info_array, tag, qualified_name_hash, die_offsets);
    return die_info_array.size();
}

size_t
DWARFMappedHash::MemoryTable::FindCompleteObjCClassByName (const char *name,
                                                           DIEArray &die_offsets,
                                                           bool must_be_implementation)
{
    DIEInfoArray die_info_array;
    if (FindByName(name, die_info_array))
    {
        if (must_be_implementation && GetHeader().header_data.ContainsAtom (eAtomTypeTypeFlags))
        {
            // If we have two atoms, then we have the DIE offset and
            // the type flags so we can find the objective C class
            // efficiently.
            DWARFMappedHash::ExtractTypesFromDIEArray (die_info_array, 
                                                       UINT32_MAX,
                                                       eTypeFlagClassIsImplementation,
                                                       die_offsets);
        }
        else
        {
            // We don't only want the one true definition, so try and see
            // what we can find, and only return class or struct DIEs.
            // If we do have the full implementation, then return it alone,
            // else return all possible matches.
            const bool return_implementation_only_if_available = true;
            DWARFMappedHash::ExtractClassOrStructDIEArray (die_info_array, 
                                                           return_implementation_only_if_available,
                                                           die_offsets);
        }
    }
    return die_offsets.size();
}

size_t 
DWARFMappedHash::MemoryTable::FindByName (const char *name, DIEInfoArray &die_info_array)
{
    if (!name || !name[0])
        return 0;

    Pair kv_pair;
    size_t old_size = die_info_array.size();
    if (Find (name, kv_pair))
    {
        die_info_array.swap(kv_pair.value);
        return die_info_array.size() - old_size;
    }
    return 0;
}
